package com.batchengine.util;

import javax.swing.table.TableModel;

/**
 * Created by sunil.incubator@yahoo.com.
 * User: sunil.kalva
 * Date: Jul 11, 2009
 * <p/>
 * <p/>
 * This class renders a table in textual form. Here is an example of using this class:
 * <pre>
 *      String[] columns = {"Name", "Address", "City", "State", "Zip Code"};
 *      Object[][] data = {
 *          {"Juei Chang", "3600 Bridge Parkway", "Redwood Shores", "CA", new Integer(94065)},
 *          {"Gautam Golwala", "1 Network Drive", "Palo Alto", "CA", "90000"},
 *          {"Harendra Verma", "100 Infinite Loop", "Cupertino", "CA", new Integer(99999)}
 *      };
 * <p/>
 *      TableModel tableModel = new DefaultTableModel(data, columns);
 *      TextTableRenderer renderer = new TextTableRenderer(tableModel);
 * <p/>
 *      logger.debug();
 *      for (; renderer.hasNext(); ) {
 *          logger.debug(renderer.getNextLine());
 *      }
 * </pre>
 */
public class TextTableRenderer {

    protected TableModel tableModel;
    protected int minColumnWidth;
    protected int maxColumnWidth;
    protected int cellBorderWidth;
    protected boolean headerSeparator;
    protected char headerSeparatorChar;
    protected boolean columnSeparator;
    protected char columnSeparatorChar;

    private int columnCount;
    private int rowCount;
    private int[] columnWidths;
    private int currentRow = 0;

    private int state = COLUMN_STATE;
    private static final int COLUMN_STATE = 0;
    private static final int HEADER_SEPARATOR_STATE = 1;
    private static final int ROW_STATE = 2;

    /**
     * Creates a renderer with the following default settings: (1) the minimum column width
     * is 1, (2) the maximum column width is 30, (3) the cell border width is 1, (4) the
     * header separator is rendered, (5) the header separator character is the '-' character,
     * (6) the column separaters are rendered, and (7) the column separator character is
     * the '|' character.
     *
     * @param tableModel the table model to render
     */
    public TextTableRenderer(TableModel tableModel) {
        this(tableModel, 1, 30, 1, true, '-', true, '|');
    }

    /**
     * Creates a renderer. The width of a particular column is the largest width of all
     * the rows and the column name in the column. If the calculated width is smaller
     * than <tt>minColumnWidth</tt>, it is adjusted to <tt>minColumnWidth</tt>. If it
     * is smaller than <tt>maxColumnWidth</tt>, it is adjusted to <tt>maxColumnWidth</tt>.
     * <p/>
     * If <tt>headerSeparator</tt> is true, a horizontal line, consisting of
     * <tt>headerSeparatorChar</tt> characters will be inserted between the column header and
     * the rows by the renderer. If <tt>columnSeparator</tt> is true, vertical lines,
     * consisting of <tt>columnSeparatorChar</tt> characters will be inserted.
     *
     * @param tableModel          the table model to render
     * @param minColumnWidth      the minimum column width of any column
     * @param maxColumnWidth      the maximum column width of any column
     * @param cellBorderWidth     ace
     * @param headerSeparator     if true, renderer will insert header separator
     * @param headerSeparatorChar the header separator character
     * @param columnSeparator     if true, renderer will insert separators between columns
     * @param columnSeparatorChar the column separator character
     */
    public TextTableRenderer(TableModel tableModel,
                             int minColumnWidth,
                             int maxColumnWidth,
                             int cellBorderWidth,
                             boolean headerSeparator,
                             char headerSeparatorChar,
                             boolean columnSeparator,
                             char columnSeparatorChar) {
        this.tableModel = tableModel;

        if (maxColumnWidth < minColumnWidth) {
            throw new IllegalArgumentException();
        }
        this.minColumnWidth = minColumnWidth;
        this.maxColumnWidth = maxColumnWidth;
        this.cellBorderWidth = cellBorderWidth;
        this.headerSeparator = headerSeparator;
        this.headerSeparatorChar = headerSeparatorChar;
        this.columnSeparator = columnSeparator;
        this.columnSeparatorChar = columnSeparatorChar;

        columnCount = tableModel.getColumnCount();
        rowCount = tableModel.getRowCount();
        columnWidths = new int[columnCount];

        calculateColumnWidths();
    }

    private static void appendSpaces(int count, StringBuffer buffer) {
        appendChars(count, buffer, ' ');
    }

    private static void appendChars(int count, StringBuffer buffer, char ch) {
        for (int i = 0; i < count; i++) {
            buffer.append(ch);
        }
    }

    private void calculateColumnWidths() {
        for (int c = 0; c < columnCount; c++) {
            columnWidths[c] = tableModel.getColumnName(c).length();
            for (int r = 0; r < rowCount; r++) {
                Object data = tableModel.getValueAt(r, c);
                if (data != null) {
                    columnWidths[c] = Math.max(columnWidths[c], data.toString().length());
                }
            }

            columnWidths[c] = Math.min(maxColumnWidth, columnWidths[c]);
            columnWidths[c] = Math.max(minColumnWidth, columnWidths[c]);
        }
    }

    private void formatCell(StringBuffer buf, String cell, int width) {
        if (cell.length() > width) {
            buf.append(cell.substring(0, width));
        } else {
            buf.append(cell);
            appendSpaces(width - cell.length(), buf);
        }
    }

    /**
     * Returns true if there are more lines to render.
     *
     * @return a
     */
    public boolean hasNext() {
        return !(state == ROW_STATE && currentRow == rowCount);
    }

    /**
     * Returns the next line to render.
     *
     * @return the next line to render
     */
    public String getNextLine() {
        if (!hasNext()) {
            return null;
        }

        StringBuffer buf = new StringBuffer();
        switch (state) {
            case COLUMN_STATE:
                // Print the columns names.
                for (int c = 0; c < columnCount; c++) {
                    String columnName = tableModel.getColumnName(c);
                    formatCell(buf, columnName, columnWidths[c]);
                    if (c < columnCount - 1) {
                        appendSpaces(cellBorderWidth, buf);
                        if (columnSeparator) {
                            buf.append(columnSeparatorChar);
                        }
                        appendSpaces(cellBorderWidth, buf);
                    }
                }

                state = (headerSeparator) ? HEADER_SEPARATOR_STATE : ROW_STATE;
                break;
            case HEADER_SEPARATOR_STATE:
                for (int c = 0; c < columnCount; c++) {
                    appendChars(columnWidths[c], buf, headerSeparatorChar);
                    if (c < columnCount - 1) {
                        appendChars(cellBorderWidth, buf, headerSeparatorChar);
                        if (columnSeparator) {
                            buf.append(headerSeparatorChar);
                        }
                        appendChars(cellBorderWidth, buf, headerSeparatorChar);
                    }
                }
                state = ROW_STATE;
                break;
            case ROW_STATE:
                for (int c = 0; c < columnCount; c++) {
                    Object data = tableModel.getValueAt(currentRow, c);
                    String cell = (data == null) ? "" : data.toString();
                    formatCell(buf, cell, columnWidths[c]);
                    if (c < columnCount - 1) {
                        appendSpaces(cellBorderWidth, buf);
                        if (columnSeparator) {
                            buf.append(columnSeparatorChar);
                        }
                        appendSpaces(cellBorderWidth, buf);
                    }
                }
                currentRow++;
                break;
        }
        return buf.toString();
    }

}
